#pragma once
#include "matrix.h"
#include "../engine/essential.h"

// ================================================================================================
// Vector class in homogenous coordinates
// ================================================================================================
struct Vector4 {
    union {
        struct {
	        float x;
	    	float y;
	    	float z;            
     	    float w;
        };
        float pt[4];
    };

    // -- default constructor
    Vector4();

    // -- lifetime
    Vector4(float x, float y, float z);
    
    // -- operator overloads
    float& operator[](uint);
    float const& operator[](uint) const;
    Vector4 operator+(const Vector4& vec);
    Vector4 operator+(const Vector4& vec) const;
    Vector4 operator+=(Vector4& vec);
    Vector4 operator+=(const Vector4& vec);
    Vector4 operator-(const Vector4& vec);
    Vector4 operator-(const Vector4& vec) const;
    Vector4 operator-=(Vector4& vec);
    Vector4 operator-=(const Vector4& vec);
    Vector4 operator*(const Matrix4x4& mat);
    Vector4 operator*(const Matrix4x4& mat) const;
    Vector4 operator*=(Matrix4x4& mat);
    Vector4 operator*=(const Matrix4x4& mat);
    Vector4 operator*(const float num);
    Vector4 operator*(const float num) const;
    Vector4 operator*=(float num);

    // -- negation operator
    Vector4 operator-() const;
};

// ================================================================================================
// Constructors
// ================================================================================================
inline Vector4::Vector4() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {
}

inline Vector4::Vector4(float x_, float y_, float z_) : x(x_), y(y_), z(z_), w(1.0f) {
}

// ================================================================================================
// Operator overloads
// ================================================================================================
inline float& Vector4::operator[](uint index) {
    return pt[index];
}

inline float const& Vector4::operator[](uint index) const {
    return pt[index];
}

inline Vector4 Vector4::operator+(const Vector4& vec) {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    return temp;
}

inline Vector4 Vector4::operator+(const Vector4& vec) const {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    return temp;
}

inline Vector4 Vector4::operator+=(Vector4& vec) {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator+=(const Vector4& vec) {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator-(const Vector4& vec) {
    Vector4 temp;

    temp.x = x - vec.x;
    temp.y = y - vec.y;
    temp.z = z - vec.z;

    return temp;
}

inline Vector4 Vector4::operator-(const Vector4& vec) const{
    Vector4 temp;

    temp.x = x - vec.x;
    temp.y = y - vec.y;
    temp.z = z - vec.z;

    return temp;
}

inline Vector4 Vector4::operator-=(Vector4& vec) {
    Vector4 temp;

    temp.x = x - vec.x;
    temp.y = y - vec.y;
    temp.z = z - vec.z;

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator*(const Matrix4x4& mat) {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    return temp;
}

inline Vector4 Vector4::operator*(const Matrix4x4& mat) const {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    return temp;
}

inline Vector4 Vector4::operator*=(Matrix4x4& mat) {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator*=(const Matrix4x4& mat) {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator*(float num) {
    Vector4 temp;

    temp.x = x * num;
    temp.y = y * num;
    temp.z = z * num;
    
    return temp;
}

inline Vector4 Vector4::operator*(float num) const {
    Vector4 temp;

    temp.x = x * num;
    temp.y = y * num;
    temp.z = z * num;
    
    return temp;
}

inline Vector4 Vector4::operator*=(float num) {
    Vector4 temp;

    temp.x = x * num;
    temp.y = y * num;
    temp.z = z * num;

    *this = temp;
    
    return temp;
}

// ================================================================================================
// Negation operator
// ================================================================================================
inline Vector4 Vector4::operator-() const {
    return Vector4(-x, -y, -z);
}

// ================================================================================================
// Scalar and vector multiplication these are for symmetry
// ================================================================================================
inline Vector4 operator*(float num,const Vector4& vec) {
    Vector4 temp;

    temp.x = vec.x * num;
    temp.y = vec.y * num;
    temp.z = vec.z * num;

    return temp;
}

inline Vector4 operator*=(float num, Vector4& vec) {
    Vector4 temp;

    temp.x = vec.x * num;
    temp.y = vec.y * num;
    temp.z = vec.z * num;

	vec = temp;

    return temp;
}
